"""
下载核心功能模块 - 处理各种平台的下载逻辑
"""

import requests
import re
import json
import os
import time
import random
from urllib.parse import unquote
from io import BytesIO
from concurrent.futures import ThreadPoolExecutor
from bs4 import BeautifulSoup

from config import HEADERS, IMG_HEADERS, IMG_COOKIES, DEFAULT_COOKIES, KUAISHOU_CONFIG, DOWNLOAD_CONFIG, BILIBILI_DEFAULT_COOKIES, BILIBILI_HEADERS
from cookie_manager import CookieManager


class DownloaderCore:
    """下载核心功能类"""

    def __init__(self):
        self.headers = HEADERS.copy()
        self.img_headers = IMG_HEADERS.copy()
        self.img_cookies = IMG_COOKIES.copy()
        self.default_cookies = DEFAULT_COOKIES.copy()
        self.kuaishou_config = KUAISHOU_CONFIG.copy()
        self.bilibili_cookies = BILIBILI_DEFAULT_COOKIES.copy()
        self.bilibili_headers = BILIBILI_HEADERS.copy()

        # 优化session配置
        self.session = self._create_optimized_session()

        # 初始化Cookie管理器
        self.cookie_manager = CookieManager()

        # 启动时加载保存的Cookie
        self._load_saved_cookies()

    def _create_optimized_session(self):
        """创建优化的session"""
        session = requests.Session()

        # 配置连接池
        from requests.adapters import HTTPAdapter
        from urllib3.util.retry import Retry

        # 重试策略
        retry_strategy = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504],
        )

        # HTTP适配器
        adapter = HTTPAdapter(
            pool_connections=10,
            pool_maxsize=10,
            max_retries=retry_strategy
        )

        session.mount("http://", adapter)
        session.mount("https://", adapter)

        return session

    def _load_saved_cookies(self):
        """启动时加载保存的Cookie"""
        try:
            # 加载抖音Cookie
            douyin_cookies = self.cookie_manager.load_douyin_cookies()
            if douyin_cookies:
                self.img_cookies.update(douyin_cookies)
                self.default_cookies.update(douyin_cookies)

            # 加载快手Cookie
            kuaishou_cookies = self.cookie_manager.load_kuaishou_cookies()
            if kuaishou_cookies:
                self.kuaishou_config["cookies"].update(kuaishou_cookies)

            # 加载B站Cookie
            bilibili_cookies = self.cookie_manager.load_bilibili_cookies()
            if bilibili_cookies:
                self.bilibili_cookies.update(bilibili_cookies)
        except Exception as e:
            pass  # 静默处理Cookie加载错误

    def set_custom_cookies(self, cookies_dict, platform="douyin"):
        """设置自定义Cookie并保存到文件

        Args:
            cookies_dict: Cookie字典
            platform: 平台名称 ("douyin" 或 "kuaishou")
        """
        if not cookies_dict:
            return

        try:
            if platform == "douyin":
                # 更新内存中的Cookie
                self.img_cookies.update(cookies_dict)
                self.default_cookies.update(cookies_dict)

                # 保存到文件
                self.cookie_manager.update_douyin_cookies(cookies_dict)

            elif platform == "kuaishou":
                # 更新内存中的Cookie
                self.kuaishou_config["cookies"].update(cookies_dict)

                # 保存到文件
                self.cookie_manager.update_kuaishou_cookies(cookies_dict)

            elif platform == "bilibili":
                # 更新内存中的Cookie
                self.bilibili_cookies.update(cookies_dict)

                # 保存到文件
                self.cookie_manager.update_bilibili_cookies(cookies_dict)

        except Exception as e:
            pass  # 静默处理Cookie设置错误

    def parse_and_set_cookies(self, cookie_string, platform="douyin"):
        """解析Cookie字符串并设置

        Args:
            cookie_string: Cookie字符串
            platform: 平台名称 ("douyin" 或 "kuaishou")
        """
        try:
            cookies_dict = self.cookie_manager.parse_cookie_string(cookie_string)
            self.set_custom_cookies(cookies_dict, platform)
            return len(cookies_dict)
        except Exception as e:
            return 0
            
    def get_video_url_and_download(self, url, save_path, progress_callback=None, log_callback=None):
        """获取抖音视频URL并下载（优化版）"""
        try:
            if log_callback:
                log_callback(f"开始处理链接: {url}")

            # 提取modal_id用于精确匹配
            target_modal_id = None
            modal_match = re.search(r'modal_id=(\d+)', url)
            if modal_match:
                target_modal_id = modal_match.group(1)
                if log_callback:
                    log_callback(f"检测到目标视频ID: {target_modal_id}")

            # 处理分享链接
            if "v.douyin.com" in url or "iesdouyin.com" in url:
                if log_callback:
                    log_callback("检测到短链接，正在解析...")
                response = self.session.get(url, headers=self.headers, allow_redirects=True)
                url = response.url
                if log_callback:
                    log_callback(f"短链接解析为: {url}")

            # 直接使用链接转换方案（原第二种方法）
            if log_callback:
                log_callback("正在获取页面数据...")

            # 首先尝试链接转换
            converted_url = self._try_convert_url(url, log_callback)
            response = None
            if converted_url and converted_url != url:
                if log_callback:
                    log_callback(f"尝试转换后的链接: {converted_url}")
                try:
                    # 对于转换后的链接，我们直接使用带Cookie的请求
                    if log_callback:
                        log_callback("使用带Cookie请求转换后的链接...")
                    response = self.session.get(converted_url, headers=self.headers, cookies=self.default_cookies)

                    if response.status_code == 200:
                        if log_callback:
                            log_callback(f"页面请求成功，状态码: {response.status_code}")
                            log_callback(f"页面内容长度: {len(response.text)} 字符")
                        if "验证码中间页" in response.text or "TTGCaptcha" in response.text:
                            if log_callback:
                                log_callback("转换后的链接也遇到验证码页面")
                        else:
                            if log_callback:
                                log_callback("正在解析页面数据...")
                                log_callback("正在提取视频链接...")
                            video_urls = self.extract_play_api(response.text, target_modal_id)
                            if video_urls:
                                if log_callback:
                                    log_callback("链接转换方案成功！")
                                    log_callback(f"找到 {len(video_urls)} 个视频链接")
                                    for i, video_url in enumerate(video_urls):
                                        log_callback(f"视频链接 {i+1}: {video_url[:100]}...")
                            else:
                                if log_callback:
                                    log_callback("转换后的链接未找到视频数据")
                                    cleaned_text = response.text.replace('\x00', '')
                                    if len(cleaned_text) < 10000:
                                        log_callback(f"页面内容预览: {cleaned_text[:2000]}")
                                    else:
                                        log_callback("页面内容过长，无法显示预览")                   
                    else:
                        if log_callback:
                            log_callback(f"请求失败，状态码: {response.status_code}")
                except Exception as e:
                    if log_callback:
                        log_callback(f"链接转换方案失败: {str(e)}")
                    video_urls = []
            else:
                video_urls = []

                # 直接使用原始链接尝试获取
                if log_callback:
                    log_callback("使用原始链接直接请求...")
                try:
                    response = self.session.get(url, headers=self.headers, cookies=self.default_cookies)
                    if log_callback:
                        log_callback(f"原始链接请求状态码: {response.status_code}")
                        log_callback(f"原始链接请求内容长度: {len(response.text)} 字符")
                    if response.status_code == 200:
                        if log_callback:
                            log_callback("正在从原始链接提取视频...")
                        video_urls = self.extract_play_api(response.text, target_modal_id)
                        if video_urls:
                            if log_callback:
                                log_callback(f"从原始链接找到 {len(video_urls)} 个视频链接")
                                for i, video_url in enumerate(video_urls):
                                    log_callback(f"视频链接 {i+1}: {video_url[:100]}...")
                        else:
                            if log_callback:
                                log_callback("从原始链接未找到视频数据")
                                cleaned_text = response.text.replace('\x00', '')
                                if len(cleaned_text) < 5000:
                                    log_callback(f"原始链接页面内容预览: {cleaned_text[:1000]}")
                                else:
                                    log_callback("原始链接页面内容过长，无法显示预览")
                    else:
                        if log_callback:
                            log_callback(f"原始链接请求失败，状态码: {response.status_code}")
                except Exception as e:
                    if log_callback:
                        log_callback(f"原始链接请求失败: {str(e)}")

            # 如果链接转换失败，尝试其他备用方案
            if not video_urls:
                if log_callback:
                    log_callback("链接转换未找到视频链接，尝试备用方案...")
                video_urls = self._try_alternative_methods(url, log_callback, target_modal_id)

            if not video_urls:
                if log_callback:
                    log_callback("所有方法都未找到视频链接，可能原因：")
                    log_callback("1. 链接可能是用户主页而非具体视频")
                    log_callback("2. 抖音页面结构已更新")
                    log_callback("3. 需要更新Cookie或请求头")
                    log_callback("4. 可能遇到了验证码页面")
                    log_callback("请尝试使用具体的视频链接，或联系开发者更新程序")
                    if response is not None and hasattr(response, 'text'):
                        cleaned_text = response.text.replace('\x00', '')
                        if len(cleaned_text) < 5000:
                            log_callback(f"最终页面内容预览: {cleaned_text[:1000]}")
                raise Exception("未找到视频链接")

            video_url = video_urls[0]
            if log_callback:
                log_callback(f"成功获取到视频链接: {video_url[:50]}...")
                log_callback("开始下载视频...")

            # 生成文件名
            filename = f"douyin_video_{int(time.time())}.mp4"
            filepath = os.path.join(save_path, filename)

            # 直接下载视频（不需要线程池，因为已经在下载线程中）
            # download_video_with_progress 方法内部已经处理了日志输出，这里不需要重复输出
            self.download_video_with_progress(video_url, filepath, progress_callback, log_callback)

        except Exception as e:
            import traceback
            error_details = traceback.format_exc()
            if log_callback:
                log_callback(f"下载过程中出错: {str(e)}")
                log_callback(f"错误详情: {error_details}")
            # 记录到日志文件
            try:
                import logging
                logging.error(f"抖音视频下载失败: {str(e)}\n{error_details}")
            except:
                pass
            raise
    
    def extract_play_api(self, html_content, target_modal_id=None):
        """提取视频playApi链接 - 支持多种抖音页面结构"""
        result = []
        found_videos = []  # 存储所有找到的视频信息

        def search_videos(obj, path=""):
            if isinstance(obj, dict):
                # 检查是否是视频对象，并收集相关信息
                video_info = {}

                # 尝试获取视频ID
                if 'awemeId' in obj:
                    video_info['awemeId'] = obj['awemeId']
                elif 'id' in obj and isinstance(obj['id'], str) and len(obj['id']) > 10:
                    video_info['awemeId'] = obj['id']

                # 方法1: 查找标准的video.bitRateList结构
                if 'video' in obj and isinstance(obj['video'], dict):
                    video_data = obj['video']
                    if 'bitRateList' in video_data and isinstance(video_data['bitRateList'], list):
                        for item in video_data['bitRateList']:
                            if isinstance(item, dict) and 'playApi' in item:
                                video_info['playApi'] = item['playApi']
                                found_videos.append(video_info)
                                if not target_modal_id or video_info.get('awemeId') == target_modal_id:
                                    result.append(item['playApi'])
                                    return True

                # 方法2: 查找playAddr结构（新版本可能使用）
                if 'playAddr' in obj and isinstance(obj['playAddr'], list):
                    for addr in obj['playAddr']:
                        if isinstance(addr, dict) and 'src' in addr:
                            video_info['playApi'] = addr['src']
                            found_videos.append(video_info)
                            if not target_modal_id or video_info.get('awemeId') == target_modal_id:
                                result.append(addr['src'])
                                return True

                # 方法3: 查找直接的playApi字段
                if 'playApi' in obj:
                    video_info['playApi'] = obj['playApi']
                    found_videos.append(video_info)
                    if not target_modal_id or video_info.get('awemeId') == target_modal_id:
                        result.append(obj['playApi'])
                        return True

                # 方法4: 查找src字段（备用）
                if 'src' in obj and isinstance(obj['src'], str) and obj['src'].startswith('http'):
                    video_info['playApi'] = obj['src']
                    found_videos.append(video_info)
                    if not target_modal_id or video_info.get('awemeId') == target_modal_id:
                        result.append(obj['src'])
                        return True

                # 方法5: 查找urlList结构
                if 'urlList' in obj and isinstance(obj['urlList'], list):
                    for url_item in obj['urlList']:
                        if isinstance(url_item, str) and url_item.startswith('http'):
                            video_info['playApi'] = url_item
                            found_videos.append(video_info)
                            if not target_modal_id or video_info.get('awemeId') == target_modal_id:
                                result.append(url_item)
                                return True

                # 继续递归查找
                for key, value in obj.items():
                    if search_videos(value, f"{path}.{key}" if path else key):
                        return True
            elif isinstance(obj, list):
                for i, item in enumerate(obj):
                    if search_videos(item, f"{path}[{i}]" if path else f"[{i}]"):
                        return True
            return False

        try:
            # 查找RENDER_DATA
            data_match = re.findall(r'<script id="RENDER_DATA" type="application/json">(.*?)</script>', html_content)
            if data_match:
                decode_string = unquote(data_match[0])
                json_data = json.loads(decode_string)
                search_videos(json_data)
                if result:
                    return result

            # 如果RENDER_DATA没找到，尝试查找其他数据源
            # 查找window._ROUTER_DATA
            router_match = re.search(r'window\._ROUTER_DATA\s*=\s*({.*?});', html_content, re.DOTALL)
            if router_match:
                try:
                    router_data = json.loads(router_match.group(1))
                    search_videos(router_data)
                    if result:
                        return result
                except:
                    pass

            # 查找window.pageData
            page_match = re.search(r'window\.pageData\s*=\s*({.*?});', html_content, re.DOTALL)
            if page_match:
                try:
                    page_data = json.loads(page_match.group(1))
                    search_videos(page_data)
                    if result:
                        return result
                except:
                    pass

            # 如果没有找到匹配的视频，但找到了其他视频，且指定了target_modal_id
            # 作为回退方案，返回找到的第一个视频
            if not result and target_modal_id and found_videos:
                if found_videos[0].get('playApi'):
                    result.append(found_videos[0]['playApi'])

            return result
        except Exception as e:
            return []

    def _try_convert_url(self, url, log_callback=None):
        """尝试将用户主页链接转换为精选页面链接"""
        try:
            # 检查是否是用户主页链接且包含modal_id
            if "/user/" in url and "modal_id=" in url:
                if log_callback:
                    log_callback("检测到用户主页链接，尝试转换为精选页面格式...")

                # 提取modal_id
                modal_match = re.search(r'modal_id=(\d+)', url)
                if modal_match:
                    modal_id = modal_match.group(1)
                    # 转换为精选页面格式
                    converted_url = f"https://www.douyin.com/jingxuan?modal_id={modal_id}"
                    if log_callback:
                        log_callback(f"转换链接: {url} -> {converted_url}")
                    return converted_url

            # 如果不是用户主页链接或没有modal_id，返回原链接
            return url

        except Exception as e:
            if log_callback:
                log_callback(f"链接转换出错: {str(e)}")
            return url

    def _try_convert_img_url(self, url, log_callback=None):
        """尝试将抖音图片链接转换为精选页面链接"""
        try:
            # 检查是否已经是精选页面格式
            if "/jingxuan?modal_id=" in url:
                if log_callback:
                    log_callback("链接已经是精选页面格式，无需转换")
                return url

            # 提取modal_id
            modal_match = re.search(r'modal_id=(\d+)', url)
            if modal_match:
                modal_id = modal_match.group(1)
                # 转换为精选页面格式
                converted_url = f"https://www.douyin.com/jingxuan?modal_id={modal_id}"
                if log_callback:
                    log_callback(f"检测到modal_id，转换链接格式...")
                    log_callback(f"原链接: {url}")
                    log_callback(f"转换后: {converted_url}")
                return converted_url
            else:
                if log_callback:
                    log_callback("未找到modal_id，保持原链接")
                return url

        except Exception as e:
            if log_callback:
                log_callback(f"图片链接转换出错: {str(e)}")
            return url

    def _try_alternative_methods(self, url, log_callback=None, target_modal_id=None):
        """尝试备用方案获取视频链接"""
        result = []

        try:
            # 备用方案1: 使用不同的请求头（模拟更真实的浏览器）
            if log_callback:
                log_callback("尝试备用方案1: 使用增强请求头...")

            enhanced_headers = {
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
                "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
                "Accept-Encoding": "gzip, deflate, br",
                "Cache-Control": "no-cache",
                "Connection": "keep-alive",
                "DNT": "1",
                "Pragma": "no-cache",
                "Sec-Fetch-Dest": "document",
                "Sec-Fetch-Mode": "navigate",
                "Sec-Fetch-Site": "none",
                "Sec-Fetch-User": "?1",
                "Upgrade-Insecure-Requests": "1",
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
                "sec-ch-ua": '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
                "sec-ch-ua-mobile": "?0",
                "sec-ch-ua-platform": '"Windows"'
            }

            # 先访问主页建立会话
            try:
                self.session.get("https://www.douyin.com/", headers=enhanced_headers, timeout=10)
            except:
                pass  # 忽略主页访问失败

            # 使用增强请求头重新请求
            response = self.session.get(url, headers=enhanced_headers)

            if response.status_code == 200:
                # 检查是否是验证码页面
                if "验证码中间页" in response.text or "TTGCaptcha" in response.text:
                    if log_callback:
                        log_callback("遇到验证码页面，备用方案1失败")
                else:
                    result = self.extract_play_api(response.text, target_modal_id)
                    if result and log_callback:
                        log_callback("备用方案1成功获取到视频链接")
                        return result

            # 备用方案2: 尝试不带Cookie的请求
            if not result:
                if log_callback:
                    log_callback("尝试备用方案2: 不带Cookie的请求...")

                response = self.session.get(url, headers=enhanced_headers)
                if response.status_code == 200 and "验证码中间页" not in response.text:
                    result = self.extract_play_api(response.text, target_modal_id)
                    if result and log_callback:
                        log_callback("备用方案2成功获取到视频链接")

            return result

        except Exception as e:
            if log_callback:
                log_callback(f"备用方案执行出错: {str(e)}")
            return []
    
    def get_douyin_img(self, url, save_path, progress_callback=None, log_callback=None):
        """获取抖音图片并下载"""
        try:
            # 确保保存目录存在
            if not os.path.exists(save_path):
                os.makedirs(save_path)
                if log_callback:
                    log_callback(f"创建保存目录: {save_path}")

            # 处理可能的复制粘贴链接格式清理
            url_pattern = re.search(r'https?://[^\s]+', url)
            if url_pattern:
                url = url_pattern.group(0)
                if log_callback:
                    log_callback(f"从输入中提取链接: {url}")

            # 验证链接类型
            if "/user/" in url and "modal_id=" not in url:
                if log_callback:
                    log_callback("检测到用户主页链接，图片下载需要具体的图片帖子链接")
                    log_callback("请复制具体的图片帖子链接，而不是用户主页链接")
                    log_callback("图片帖子链接通常包含多张图片的轮播内容")
                raise Exception("请提供具体的图片帖子链接，而不是用户主页链接")

            # 处理抖音短链接
            if "v.douyin.com" in url or "iesdouyin.com" in url:
                if log_callback:
                    log_callback(f"检测到抖音短链接: {url}")
                try:
                    # 发送请求获取重定向的URL
                    response = self.session.head(url, headers=self.img_headers, allow_redirects=True)
                    if response.status_code == 200 or response.status_code == 302:
                        final_url = response.url
                        if log_callback:
                            log_callback(f"短链接已解析为: {final_url}")
                        url = final_url
                    else:
                        if log_callback:
                            log_callback(f"解析短链接失败，状态码: {response.status_code}，将继续使用原链接")
                except Exception as e:
                    if log_callback:
                        log_callback(f"解析短链接时出错: {str(e)}，将继续使用原链接")

            # 确保URL是完整的抖音链接
            if not url.startswith("http"):
                url = "https://" + url

            # 尝试转换链接为精选页面格式
            converted_url = self._try_convert_img_url(url, log_callback)
            if converted_url != url:
                url = converted_url

            # 检查是否包含modal_id参数（图片帖子的标识）
            if "modal_id=" in url:
                if log_callback:
                    log_callback("检测到图片帖子链接，开始处理...")
            else:
                if log_callback:
                    log_callback("警告：链接可能不是图片帖子，将尝试解析...")

            if log_callback:
                log_callback(f"使用链接: {url}")

            # 使用图片专用请求头和Cookie
            if log_callback:
                log_callback("正在请求抖音页面数据...")
            response = self.session.get(url, headers=self.img_headers, cookies=self.img_cookies)

            # 输出更详细的调试信息
            if log_callback:
                log_callback(f"请求状态码: {response.status_code}")
            if response.status_code != 200:
                if log_callback:
                    log_callback("请求失败，尝试查看响应内容...")
                    log_callback(f"响应内容预览: {response.text[:200]}...")
                raise Exception(f"请求失败，状态码: {response.status_code}")

            if log_callback:
                log_callback("正在提取图片信息...")

            # 提取图片数据
            self.extract_img(response.text, save_path, progress_callback, log_callback)

        except Exception as e:
            if log_callback:
                log_callback(f"图片下载失败: {str(e)}")
            raise
    
    def extract_img(self, html_content, save_path, progress_callback=None, log_callback=None):
        """递归获取所有images中urlList的第一个链接"""
        result = []
        found_count = 0
        total_count = 0

        def search_images(obj):
            nonlocal found_count, total_count
            if isinstance(obj, dict):
                # 如果是字典类型
                for key, value in obj.items():
                    if key == 'images':
                        # 找到images字段
                        if isinstance(value, list):
                            total_count += len(value)
                            for item in value:
                                if isinstance(item, dict) and 'urlList' in item:
                                    # 获取urlList中的第一个链接
                                    if item['urlList'] and len(item['urlList']) > 0:
                                        img_url = item['urlList'][0]
                                        if log_callback:
                                            log_callback(f"找到图片链接: {img_url[:50]}...")
                                        result.append(img_url)
                                        # 下载图片
                                        try:
                                            if self.download_single_img_with_callback(img_url, save_path, log_callback):
                                                found_count += 1
                                                # 更新进度，将进度转换为0-100之间的值
                                                progress = min(found_count / max(total_count, 1), 1.0) * 100
                                                if progress_callback:
                                                    progress_callback(progress)
                                        except Exception as e:
                                            if log_callback:
                                                log_callback(f"下载图片时出错: {str(e)}")
                    else:
                        # 继续递归搜索
                        search_images(value)
            elif isinstance(obj, list):
                for item in obj:
                    search_images(item)

        try:
            # 提取JSON数据
            if log_callback:
                log_callback("正在提取数据...")
            pattern = r'<script id="RENDER_DATA" type="application/json">(.*?)</script>'
            matches = re.findall(pattern, html_content)

            if not matches:
                if log_callback:
                    log_callback("未找到RENDER_DATA脚本标签")
                    # 尝试查找其他可能的数据源
                    if "window._ROUTER_DATA" in html_content:
                        log_callback("发现window._ROUTER_DATA，但当前版本不支持解析")
                    if "window.pageData" in html_content:
                        log_callback("发现window.pageData，但当前版本不支持解析")
                    log_callback("请确保链接是图片帖子链接，而不是用户主页或视频链接")
                raise Exception("未找到数据！请检查链接类型或更新Cookie")

            # 解码和解析JSON
            data = unquote(matches[0])
            json_data = json.loads(data)

            if log_callback:
                log_callback("JSON数据解析成功，开始搜索图片链接...")

            # 开始递归搜索
            search_images(json_data)

            if not result:
                if log_callback:
                    log_callback("在JSON数据中未找到images字段")
                    log_callback("这可能是视频帖子而不是图片帖子")
                    log_callback("请确保链接指向的是包含多张图片的轮播帖子")
                raise Exception("未找到图片数据，请确认链接是图片帖子")
            else:
                if log_callback:
                    log_callback(f"共找到并下载了 {len(result)} 张图片")
                if progress_callback:
                    progress_callback(100)

        except json.JSONDecodeError as e:
            if log_callback:
                log_callback(f"JSON解析错误: {str(e)}")
            raise Exception(f"JSON解析错误: {str(e)}")
        except Exception as e:
            if log_callback:
                log_callback(f"提取图片失败: {str(e)}")
            raise
    
    def download_single_img(self, url, filepath):
        """下载单张图片"""
        try:
            response = self.session.get(url, headers=self.img_headers, cookies=self.img_cookies, timeout=DOWNLOAD_CONFIG['timeout'])
            response.raise_for_status()

            with open(filepath, 'wb') as f:
                f.write(response.content)

        except Exception as e:
            raise Exception(f"下载图片失败: {str(e)}")

    def download_single_img_with_callback(self, url, save_path, log_callback=None):
        """下载单张图片（带回调）- 优化版本"""
        max_retries = 2  # 图片下载重试次数较少
        retry_delay = 0.5

        for attempt in range(max_retries):
            try:
                # 确保保存目录存在
                if not os.path.exists(save_path):
                    os.makedirs(save_path, exist_ok=True)

                res = self.session.get(url, headers=self.img_headers, cookies=self.img_cookies, timeout=15)

                if res.status_code == 200:
                    # 检查内容类型
                    content_type = res.headers.get('content-type', '').lower()
                    if 'image' not in content_type and len(res.content) < 1024:
                        if log_callback:
                            log_callback(f"下载的不是有效图片文件")
                        if attempt < max_retries - 1:
                            time.sleep(retry_delay)
                            continue
                        return False

                    # 使用时间戳和6位随机数字作为文件名
                    prefix = f"{int(time.time())}_{random.randint(100000, 999999)}"

                    # 根据内容类型确定文件扩展名
                    if 'jpeg' in content_type or 'jpg' in content_type:
                        ext = '.jpg'
                    elif 'png' in content_type:
                        ext = '.png'
                    elif 'webp' in content_type:
                        ext = '.webp'
                    else:
                        ext = '.jpg'  # 默认

                    filepath = os.path.join(save_path, f"{prefix}{ext}")

                    with open(filepath, "wb") as f:
                        f.write(res.content)

                    if log_callback:
                        log_callback(f"保存图片: {os.path.basename(filepath)}")
                    return True

                elif res.status_code == 403:
                    if log_callback:
                        log_callback(f"图片访问被拒绝，可能需要更新Cookie")
                    return False
                elif res.status_code == 404:
                    if log_callback:
                        log_callback(f"图片不存在")
                    return False
                else:
                    if log_callback:
                        log_callback(f"下载图片失败，状态码: {res.status_code}")
                    if attempt < max_retries - 1:
                        time.sleep(retry_delay)
                        continue
                    return False

            except requests.exceptions.Timeout:
                if log_callback:
                    log_callback(f"图片下载超时 (第{attempt + 1}次尝试)")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay)
                    continue
                return False

            except Exception as e:
                if log_callback:
                    log_callback(f"下载图片时出错: {str(e)}")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay)
                    continue
                return False

        return False
    
    def download_kuaishou_video(self, url, save_path, progress_callback=None, log_callback=None):
        """下载快手视频 - 按照原版逻辑"""
        try:
            if log_callback:
                log_callback(f"快手开始处理链接: {url}")

            # 发送请求获取页面内容
            response = self.session.get(
                url,
                headers=self.kuaishou_config["headers"],
                cookies=self.kuaishou_config["cookies"],
                params=self.kuaishou_config["params"]
            )
            response.raise_for_status()

            # 使用正则表达式提取数据
            pattern = r'window\.__APOLLO_STATE__=({.*?})\s*;'
            matches = re.search(pattern, response.text, re.DOTALL)

            if matches:
                data = matches.group(1)  # 获取第一个捕获组
                json_data = json.loads(data)

                # 提取视频链接
                video_urls = self.extract_kuaishou_video_url(json_data)

                if video_urls:
                    selected_video = video_urls[0]  # 使用第一个找到的URL
                    if log_callback:
                        log_callback(f"快手找到视频链接: {selected_video['url'][:100]}...")

                    # 从URL中提取视频ID作为文件名
                    video_id = url.split('/')[-1].split('?')[0]
                    filename = f"快手_{video_id}.mp4"
                    filepath = os.path.join(save_path, filename)

                    # 下载视频
                    self.download_kuaishou_video_with_progress(selected_video['url'], filepath, progress_callback, log_callback)
                else:
                    if log_callback:
                        log_callback("快手未找到视频链接")
                    raise Exception("未找到视频链接")
            else:
                if log_callback:
                    log_callback("快手未找到匹配的数据")
                raise Exception("未找到匹配的数据")

        except requests.RequestException as e:
            if log_callback:
                log_callback(f"快手网络请求错误: {str(e)}")
            raise Exception(f"网络请求错误: {str(e)}")
        except json.JSONDecodeError as e:
            if log_callback:
                log_callback(f"快手JSON解析错误: {str(e)}")
            raise Exception(f"JSON解析错误: {str(e)}")
        except Exception as e:
            if log_callback:
                log_callback(f"快手下载过程中出错: {str(e)}")
            raise
    
    def extract_kuaishou_video_url(self, json_data):
        """提取快手视频URL - 按照原版逻辑"""
        video_urls = []

        def search_video_resource(data):
            if isinstance(data, dict):
                # 检查是否包含videoResource
                if 'videoResource' in data:
                    try:
                        video_resource = data['videoResource']
                        if isinstance(video_resource, dict) and 'json' in video_resource:
                            # 尝试解析json字段
                            if isinstance(video_resource['json'], str):
                                resource_json = json.loads(video_resource['json'])
                            else:
                                resource_json = video_resource['json']

                            # 获取h264格式的视频URL
                            if 'h264' in resource_json:
                                h264_data = resource_json['h264']
                                if 'adaptationSet' in h264_data and len(h264_data['adaptationSet']) > 0:
                                    adaptation_set = h264_data['adaptationSet'][0]
                                    if 'representation' in adaptation_set and len(adaptation_set['representation']) > 0:
                                        representation = adaptation_set['representation'][0]
                                        if 'url' in representation:
                                            video_urls.append({
                                                'quality': 'h264',
                                                'url': representation['url']
                                            })
                                            return True  # 找到URL后立即返回
                    except Exception:
                        pass

                # 如果没有找到URL，继续递归搜索
                for value in data.values():
                    if isinstance(value, (dict, list)):
                        if search_video_resource(value):
                            return True
            elif isinstance(data, list):
                for item in data:
                    if isinstance(item, (dict, list)):
                        if search_video_resource(item):
                            return True
            return False

        search_video_resource(json_data)
        return video_urls

    def download_kuaishou_video_with_progress(self, url, filepath, progress_callback=None, log_callback=None):
        """快手视频下载函数，支持进度条显示 - 按照原版逻辑"""
        try:
            # 先发送HEAD请求获取文件大小
            head_response = self.session.head(url, headers=self.kuaishou_config["download_headers"], allow_redirects=True)
            if head_response.status_code == 302:
                url = head_response.headers['Location']
                if log_callback:
                    log_callback(f"快手获取到重定向链接: {url[:100]}...")
                head_response = self.session.head(url, headers=self.kuaishou_config["download_headers"])

            total_size = int(head_response.headers.get('content-length', 0))

            if total_size == 0:
                if log_callback:
                    log_callback("快手警告：无法获取文件大小")
            else:
                if log_callback:
                    log_callback(f"快手文件大小: {total_size / 1024 / 1024:.2f}MB")

            # 开始下载
            if log_callback:
                log_callback(f"开始下载视频: {os.path.basename(filepath)}")

            with self.session.get(url, headers=self.kuaishou_config["download_headers"], stream=True) as response:
                response.raise_for_status()

                with open(filepath, 'wb') as f:
                    downloaded_size = 0
                    for chunk in response.iter_content(chunk_size=8192):
                        if chunk:
                            downloaded_size += len(chunk)
                            f.write(chunk)

                            # 更新进度条
                            if total_size and progress_callback:
                                progress = downloaded_size / total_size * 100
                                progress_callback(progress)

                # 检查文件大小
                final_size = os.path.getsize(filepath)
                if final_size == 0:
                    if log_callback:
                        log_callback("快手下载的文件大小为0，下载失败")
                    os.remove(filepath)  # 删除空文件
                    raise Exception("下载的文件大小为0，下载失败")
                elif total_size and final_size != total_size:
                    if log_callback:
                        log_callback(f"快手警告：文件大小不匹配（预期：{total_size}，实际：{final_size}）")

                # 完成下载
                if progress_callback:
                    progress_callback(100)
                if log_callback:
                    log_callback(f"下载完成: {os.path.basename(filepath)}")
                return True

        except Exception as e:
            if log_callback:
                log_callback(f"下载失败: {str(e)}")
            if os.path.exists(filepath):
                os.remove(filepath)  # 删除可能存在的不完整文件
            raise Exception(f"下载失败: {str(e)}")

    def download_tiefenquan_video(self, url, save_path, progress_callback=None, log_callback=None):
        """下载铁粉圈视频"""
        try:
            if log_callback:
                log_callback(f"开始处理铁粉圈视频链接: {url}")

            # 确保保存目录存在
            if not os.path.exists(save_path):
                os.makedirs(save_path, exist_ok=True)
                if log_callback:
                    log_callback(f"创建保存目录: {save_path}")

            # 设置铁粉圈专用请求头
            tiefenquan_headers = {
                "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
                "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
                "cache-control": "max-age=0",
                "priority": "u=0, i",
                "referer": "https://www.tiefenquan1.com/tags/%E9%B1%BC%E7%A5%9E/",
                "sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Microsoft Edge\";v=\"139\", \"Chromium\";v=\"139\"",
                "sec-ch-ua-mobile": "?0",
                "sec-ch-ua-platform": "\"Windows\"",
                "sec-fetch-dest": "document",
                "sec-fetch-mode": "navigate",
                "sec-fetch-site": "same-origin",
                "sec-fetch-user": "?1",
                "upgrade-insecure-requests": "1",
                "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0"
            }
            tiefenquan_cookies = {"timezone": "8"}

            if log_callback:
                log_callback("正在获取页面内容...")

            # 获取页面内容
            response = self.session.get(url, headers=tiefenquan_headers, cookies=tiefenquan_cookies, timeout=30)

            if response.status_code != 200:
                error_msg = f"获取页面失败，状态码: {response.status_code}"
                if log_callback:
                    log_callback(error_msg)
                raise Exception(error_msg)

            if log_callback:
                log_callback("正在解析视频链接...")

            # 解析页面，查找DPlayer视频链接
            html_content = response.text
            soup = BeautifulSoup(html_content, 'html.parser')
            scripts = soup.find_all('script')

            mp4_url = None
            for script in scripts:
                if 'DPlayer' in script.text and 'url:' in script.text:
                    # 使用正则表达式提取 url 的值
                    match = re.search(r'url:\s*["\']([^"\']+)["\']', script.text)
                    if match:
                        mp4_url = match.group(1)
                        if log_callback:
                            log_callback(f"找到视频链接: {mp4_url}")
                        break

            if not mp4_url:
                error_msg = "未找到视频链接，请确认链接是否正确"
                if log_callback:
                    log_callback(error_msg)
                raise Exception(error_msg)

            # 从URL中提取文件名
            filename = mp4_url.split('/')[-1]
            if not filename.endswith('.mp4'):
                filename += '.mp4'

            # 如果文件名为空或无效，使用时间戳
            if not filename or filename == '.mp4':
                filename = f"tiefenquan_video_{int(time.time())}.mp4"

            filepath = os.path.join(save_path, filename)

            if log_callback:
                log_callback(f"开始下载视频: {filename}")

            # 下载视频
            self.download_video_with_progress(mp4_url, filepath, progress_callback, log_callback)

            if log_callback:
                log_callback(f"✅ 视频下载完成: {filename}")

        except Exception as e:
            import traceback
            error_details = traceback.format_exc()
            if log_callback:
                log_callback(f"下载过程中出错: {str(e)}")
                log_callback(f"错误详情: {error_details}")
            raise

    def download_video_with_progress(self, url, filepath, progress_callback=None, log_callback=None):
        """带进度的视频下载 - 优化版本"""
        max_retries = 3
        retry_delay = 1

        for attempt in range(max_retries):
            try:
                if log_callback:
                    if attempt > 0:
                        log_callback(f"第{attempt + 1}次尝试下载: {os.path.basename(filepath)}")
                    else:
                        log_callback(f"开始下载: {os.path.basename(filepath)}")

                # 使用默认Cookie进行视频下载
                response = self.session.get(url, headers=self.headers, cookies=self.default_cookies, stream=True, timeout=DOWNLOAD_CONFIG['timeout'])

                if response.status_code == 200:
                    # 获取文件大小
                    total_size = int(response.headers.get('content-length', 0))
                    block_size = DOWNLOAD_CONFIG['chunk_size']  # 使用配置中的块大小
                    downloaded_size = 0

                    # 写入文件
                    with open(filepath, 'wb') as f:
                        for data in response.iter_content(block_size):
                            if data:  # 过滤掉keep-alive新块
                                downloaded_size += len(data)
                                f.write(data)

                                # 更新进度条
                                if total_size:
                                    progress = downloaded_size / total_size
                                    if progress_callback:
                                        progress_callback(progress * 100)

                    # 检查文件大小
                    if os.path.getsize(filepath) == 0:
                        if log_callback:
                            log_callback("下载的文件大小为0，下载失败")
                        os.remove(filepath)  # 删除空文件
                        if attempt < max_retries - 1:
                            time.sleep(retry_delay * (attempt + 1))
                            continue
                        raise Exception("下载的文件大小为0，下载失败")

                    # 完成下载
                    if progress_callback:
                        progress_callback(100)

                    if log_callback:
                        log_callback(f"下载完成: {os.path.basename(filepath)}")
                    return True  # 成功下载，返回True

                elif response.status_code == 403:
                    if log_callback:
                        log_callback("下载失败: 访问被拒绝，可能需要更新Cookie")
                    raise Exception("访问被拒绝，可能需要更新Cookie")
                elif response.status_code == 404:
                    if log_callback:
                        log_callback("下载失败: 视频文件不存在")
                    raise Exception("视频文件不存在")
                else:
                    if log_callback:
                        log_callback(f"下载失败，状态码: {response.status_code}")
                    if attempt < max_retries - 1:
                        time.sleep(retry_delay * (attempt + 1))
                        continue
                    raise Exception(f"下载视频失败，状态码: {response.status_code}")

            except requests.exceptions.Timeout:
                if log_callback:
                    log_callback(f"下载超时 (第{attempt + 1}次尝试)")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay * (attempt + 1))
                    continue
                if os.path.exists(filepath):
                    os.remove(filepath)
                raise Exception("下载超时")

            except requests.exceptions.ConnectionError:
                if log_callback:
                    log_callback(f"网络连接错误 (第{attempt + 1}次尝试)")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay * (attempt + 1))
                    continue
                if os.path.exists(filepath):
                    os.remove(filepath)
                raise Exception("网络连接错误")

            except Exception as e:
                if log_callback:
                    log_callback(f"下载出错: {str(e)}")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay * (attempt + 1))
                    continue
                if os.path.exists(filepath):
                    os.remove(filepath)
                raise Exception(f"下载失败: {str(e)}")

        return False  # 如果所有重试都失败，返回False

    def download_bilibili_video(self, url, save_path, progress_callback=None, log_callback=None):
        """下载B站视频"""
        import uuid
        import ffmpeg

        try:
            if log_callback:
                log_callback(f"开始处理B站链接: {url}")

            # 生成唯一文件ID
            file_id = str(uuid.uuid4())

            # 准备请求头，包含Cookie
            headers = self.bilibili_headers.copy()
            cookie_string = "; ".join([f"{key}={value}" for key, value in self.bilibili_cookies.items()])
            headers["Cookie"] = cookie_string

            if log_callback:
                log_callback("B站正在获取视频页面...")

            # 获取页面内容
            response = self.session.get(url, headers=headers)
            if response.status_code != 200:
                raise Exception(f"页面请求失败，状态码: {response.status_code}")

            if log_callback:
                log_callback("B站正在解析视频信息...")

            # 提取视频信息
            json_content = re.findall(r'__playinfo__=(.*?)</script>', response.text)
            if not json_content:
                raise Exception("无法找到视频播放信息")

            json_data = json.loads(json_content[0])

            # 获取音频和视频URL
            audio_url = json_data['data']['dash']['audio'][0]['baseUrl']
            video_url = json_data['data']['dash']['video'][0]['baseUrl']

            if log_callback:
                log_callback("B站正在下载音频文件...")

            # 为下载音视频文件准备特殊的请求头（防盗链）
            download_headers = headers.copy()
            download_headers.update({
                "Referer": url,  # 使用原始视频页面作为Referer
                "Origin": "https://www.bilibili.com",
                "Accept": "*/*",
                "Accept-Encoding": "identity;q=1, *;q=0",
                "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
                "Connection": "keep-alive",
                "Range": "bytes=0-"  # 支持断点续传
            })

            if log_callback:
                log_callback(f"B站音频链接: {audio_url[:50]}...")

            # 下载音频 - 使用重试机制
            if progress_callback:
                progress_callback(20)

            audio_file = os.path.join(save_path, f'{file_id}.mp3')
            if not self._download_with_retry(audio_url, audio_file, download_headers, "B站音频", log_callback):
                raise Exception("音频下载失败")

            if log_callback:
                log_callback(f"B站视频链接: {video_url[:50]}...")

            # 下载视频 - 使用重试机制
            if progress_callback:
                progress_callback(60)

            video_file = os.path.join(save_path, f'{file_id}.mp4')
            if not self._download_with_retry(video_url, video_file, download_headers, "B站视频", log_callback):
                # 清理已下载的音频文件
                if os.path.exists(audio_file):
                    os.remove(audio_file)
                raise Exception("视频下载失败")

            if log_callback:
                log_callback("B站正在合并音视频...")

            if progress_callback:
                progress_callback(80)

            # 合并音视频
            output_file = os.path.join(save_path, f'{file_id}_out.mp4')
            try:
                input_video = ffmpeg.input(video_file)
                input_audio = ffmpeg.input(audio_file)
                out = ffmpeg.output(input_video, input_audio, output_file,
                                   vcodec='copy', acodec='copy')
                ffmpeg.run(out, overwrite_output=True, quiet=True)

                # 清理临时文件
                os.remove(video_file)
                os.remove(audio_file)

                if progress_callback:
                    progress_callback(100)

                if log_callback:
                    log_callback(f"B站下载完成: {output_file}")

                return True

            except ffmpeg.Error as e:
                if log_callback:
                    log_callback(f"B站FFmpeg错误: {e}")
                # 清理临时文件
                if os.path.exists(video_file):
                    os.remove(video_file)
                if os.path.exists(audio_file):
                    os.remove(audio_file)
                raise Exception(f"音视频合并失败: {e}")

        except Exception as e:
            if log_callback:
                log_callback(f"B站下载失败: {str(e)}")
            raise e

    def _download_with_retry(self, url, file_path, headers, file_type, log_callback, timeout=60, max_retries=3):
        """带重试机制的下载函数"""
        import time

        for attempt in range(max_retries):
            try:
                if log_callback:
                    if attempt > 0:
                        log_callback(f"第{attempt + 1}次尝试下载{file_type}...")
                    else:
                        log_callback(f"正在下载{file_type}文件...")

                # 每次重试都创建新的session，避免连接复用问题
                session = self._create_optimized_session()

                response = session.get(url, headers=headers, timeout=timeout)
                if response.status_code not in [200, 206]:
                    raise Exception(f"{file_type}下载失败，状态码: {response.status_code}")

                data = response.content
                if len(data) == 0:
                    raise Exception(f"{file_type}文件大小为0")

                with open(file_path, 'wb') as f:
                    f.write(data)

                if log_callback:
                    log_callback(f"{file_type}下载完成，大小: {len(data)} 字节")

                return True

            except Exception as e:
                if log_callback:
                    log_callback(f"{file_type}下载失败 (尝试 {attempt + 1}/{max_retries}): {str(e)}")

                if attempt < max_retries - 1:
                    # 等待一段时间后重试，每次等待时间递增
                    wait_time = (attempt + 1) * 2
                    if log_callback:
                        log_callback(f"等待 {wait_time} 秒后重试...")
                    time.sleep(wait_time)
                else:
                    # 最后一次尝试失败
                    if log_callback:
                        log_callback(f"{file_type}下载彻底失败，已尝试 {max_retries} 次")
                    return False

        return False
